#include "limit_resources_manager.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <assert.h>

#include "dlist.h"

#include "log.h"

#define DEFAULT_MAX_LIMIT_RESOURCES_TYPE  1024

#define WRITE_LOG(...) //WRITE_DEBUG_LOG(__VA_ARGS__)

struct _limit_resources_manager_;
struct _limit_resources_ {
	struct _limit_resources_ * prev;
	struct _limit_resources_ * next;

	resid_t id;
	void * ptr;

	int inuse;
};

struct _limit_resources_manager_ {
	resid_t id;
	int max;

	struct _limit_resources_ * free_resources;
	struct _limit_resources_ * resources_mem_ptr;;

	struct ResourcesOperation opt;
	void * ctx;
};

////////////////////////////////////////////////////////////////////////////////
// start of base function

static struct _limit_resources_manager_ * alloc_resource_manager()
{
	struct _limit_resources_manager_* manager =
		(struct _limit_resources_manager_*)malloc(sizeof(struct _limit_resources_manager_));
	WRITE_LOG("create manager %p", manager);
	return manager;
}

static void free_resource_manager(struct _limit_resources_manager_ * manager) 
{
	if (manager) {
		WRITE_LOG("free manager %p", manager);
		free(manager);
	}
}

static int init_manager(struct _limit_resources_manager_ * manager,
		resid_t id,
		int max,
		struct ResourcesOperation * opt,
		void * ctx)
{
	WRITE_LOG("init manager %p", manager);

	if (manager == 0 || max <= 0) {
		return -1;
	}

	manager->id = INVALIDATE_ID;
	manager->ctx = 0;
	manager->free_resources = 0;
	manager->max = 0;
	memset(&manager->opt, 0, sizeof(manager->opt));
	manager->resources_mem_ptr = (struct _limit_resources_*)malloc(sizeof(struct _limit_resources_) * max);
	if (manager->resources_mem_ptr == 0) {
		return -1;
	}

	manager->id = id;
	manager->ctx = ctx;
	manager->max = max;
	if (opt) {
		manager->opt = *opt;
	}

	int i;
	for(i = 0; i < max; i++) {
		struct _limit_resources_ * resources = manager->resources_mem_ptr + i;
		resources->inuse = 0;

		resources->id = i;
		if (resources->id == INVALIDATE_ID)  {
			resources->id += max;
		}

		resources->ptr = 0;
		if (manager->opt.alloc) {
			resources->ptr = manager->opt.alloc(manager->ctx);
			WRITE_LOG("alloced resources %p(%p) of %p", resources, resources->ptr, manager);
		}
		dlist_insert_tail(manager->free_resources, resources);
	}
	return 0;
}

static void destroy_resource_manager(struct _limit_resources_manager_ * manager)
{
	if (manager == 0) return;

	WRITE_LOG("destory manager %p", manager);
	int i;
	if (manager->resources_mem_ptr) {
		if (manager->opt.free) {
			for(i = 0; i < manager->max; i++) {
				struct _limit_resources_ * res = manager->resources_mem_ptr+i;
				WRITE_LOG("free resources %p(%p) of %p", res, res->ptr, manager);
				manager->opt.free(res->ptr, manager->ctx);
			}
		}
	}

	manager->id = INVALIDATE_ID;
	manager->max = 0;
	manager->free_resources = 0;
	free(manager->resources_mem_ptr);
	memset(&manager->opt, 0, sizeof(manager->opt));
	manager->ctx = 0;
}

static struct _limit_resources_ * alloc_resources_of_manager(
		struct _limit_resources_manager_ * manager,
		int pos,
		void * init_data)
{
	if(manager == 0 || manager->free_resources == 0) {
		return 0;
	}

	struct _limit_resources_ * resources = 0; 
	if (pos < 0 || pos >= manager->max) {
		resources = manager->free_resources;
	} else {
		resources = manager->resources_mem_ptr + pos;
		if (resources->inuse == 1) {
			resources = 0;
		}
	} 

	if (resources == 0) {
		return 0;
	}

	dlist_remove(manager->free_resources, resources);
	resources->next = resources->prev = 0;
	resources->inuse = 1;

	WRITE_LOG("init resources %p(%p) of %p", resources, resources->ptr, manager);
	if (manager->opt.init) {
		if (manager->opt.init(resources->id, resources->ptr, init_data, manager->ctx) != 0) {
			dlist_insert_tail(manager->free_resources, resources);
			return 0;
		}
	}
	return resources;
}



static void release_resources_of_manager(
		struct _limit_resources_manager_ * manager,
		struct _limit_resources_ * resources)
{
	if (manager == 0 || resources == 0) {
		return;
	}

	WRITE_LOG("destory resources %p(%p) of %p", resources, resources->ptr, manager);
	if (manager->opt.destory) {
		manager->opt.destory(resources->id, resources->ptr, manager->ctx);
	}
	resources->inuse = 0;
	resources->id += manager->max;
	if (resources->id == INVALIDATE_ID) {
		resources->id += manager->max;
	}

	dlist_insert_tail(manager->free_resources, resources);
}

// end of base function
////////////////////////////////////////////////////////////////////////////////

static struct _limit_resources_manager_ * g_top_manager = 0;

static void * cb_alloc_resource_manager(void*ctx)
{
	return alloc_resource_manager();
}

static void cb_free_resources_manager(void * res, void*ctx)
{
	struct _limit_resources_manager_ * manager = (struct _limit_resources_manager_ *)res;
	free_resource_manager(manager);
}

struct _manager_create_info_ {
	int max;
	struct ResourcesOperation opt;
	void * ctx;
};

static int cb_init_resource_manager(resid_t resources_id, void * res, void * init_data, void * ctx)
{
	struct _limit_resources_manager_ * manager = (struct _limit_resources_manager_ *)res;
	struct _manager_create_info_ * info = (struct _manager_create_info_*)init_data;
	return init_manager(manager, resources_id, info->max, &info->opt, info->ctx);
}

static void cb_destory_resource_manager(resid_t resources_id, void * res, void*ctx)
{
	struct _limit_resources_manager_ * manager = (struct _limit_resources_manager_ *)res;
	destroy_resource_manager(manager);
}

resid_t create_resources_manager(int max,
		struct ResourcesOperation * opt,
		void * ctx)
{
	struct _manager_create_info_ info;
	info.max = max;
	info.opt = *opt;
	info.ctx  = ctx;
	struct _limit_resources_ * resources = alloc_resources_of_manager(g_top_manager, -1, &info);
	return resources->id;
}

static struct _limit_resources_ * get_resources_of_manager_by_id (
		struct _limit_resources_manager_ * manager,
		resid_t resources_id)
{
	if (manager == 0 || resources_id == INVALIDATE_ID) {
		return 0;
	}

	resid_t slot = resources_id % manager->max;
	struct _limit_resources_ * res = manager->resources_mem_ptr + slot;
	if (res->inuse && res->id == resources_id) {
		return res;
	}
	return 0;
}

static  struct _limit_resources_manager_ * get_resources_manager_by_id(resid_t manager_id)
{
	struct _limit_resources_ * resources = get_resources_of_manager_by_id(g_top_manager, manager_id);
	if (resources == 0) {
		return 0;
	}

	struct _limit_resources_manager_ * manager = (struct _limit_resources_manager_*)(resources->ptr);
	return manager;
}

static  struct _limit_resources_ * get_resources_by_two_id(resid_t manager_id, resid_t resources_id) 
{
	struct _limit_resources_manager_ * manager = get_resources_manager_by_id(manager_id);
	if (manager == 0) {
		return 0;
	}

	void  * data = get_resources_of_manager_by_id(manager, resources_id);
	if (data == 0) {
		return 0;
	}

	struct _limit_resources_ * resources = (struct _limit_resources_*)data;
	return resources;
}

void destory_resources_manager(resid_t manager_id)
{
	struct _limit_resources_manager_ * manager = get_resources_manager_by_id(manager_id);
	if (manager == 0) {
		return;
	}

	struct _limit_resources_ * resources = get_resources_of_manager_by_id(g_top_manager, manager_id);
	if (resources == 0) {
		return ;
	}
	release_resources_of_manager(g_top_manager, resources);
}

resid_t alloc_resource(resid_t manager_id, int pos, void * ctx)
{
	struct _limit_resources_manager_ * manager = get_resources_manager_by_id(manager_id);
	if (manager == 0) {
		return INVALIDATE_ID;
	}

	struct _limit_resources_ * resources = alloc_resources_of_manager(manager, pos, ctx);
	if (resources == 0) {
		return INVALIDATE_ID;
	}

	return resources->id;
}

void release_resources(resid_t manager_id, resid_t resources_id)
{
	struct _limit_resources_manager_ * manager = get_resources_manager_by_id(manager_id);
	if (manager == 0) {
		return ;
	}

	struct _limit_resources_ * resources = get_resources_of_manager_by_id(manager, resources_id);
	if (resources == 0) {
		return ;
	}

	release_resources_of_manager(manager, resources);
}

void * get_resources(resid_t manager_id, resid_t resources_id)
{
	struct _limit_resources_ * resources = get_resources_by_two_id(manager_id, resources_id);
	if (resources == 0) {
		return 0;
	}
	return resources->ptr;
}

void * set_resources(resid_t manager_id, resid_t resources_id, void * res)
{
	struct _limit_resources_ * resources = get_resources_by_two_id(manager_id, resources_id);
	if (resources == 0) {
		return 0;
	}
	void * old_res = resources->ptr;
	resources->ptr = res;
	return old_res;
}

////////////////////////////////////////////////////////////////////////////////

int LRMStartup(int max_resources_type)
{
	if (g_top_manager) {
		WRITE_LOG("LRMStartup is called with %d", g_top_manager->max);
		return -1;
	}

	if (max_resources_type <= 0) {
		max_resources_type = DEFAULT_MAX_LIMIT_RESOURCES_TYPE;
	}

	if (g_top_manager == 0) {
		g_top_manager = alloc_resource_manager();
		WRITE_LOG("g_top_manager = %p",g_top_manager);

		struct ResourcesOperation g_opt;
		g_opt.alloc = cb_alloc_resource_manager;
		g_opt.free = cb_free_resources_manager;
		g_opt.init = cb_init_resource_manager;
		g_opt.destory = cb_destory_resource_manager;

		if (init_manager(g_top_manager, INVALIDATE_ID, max_resources_type, &g_opt, 0) != 0) {
			free(g_top_manager);
			g_top_manager = 0;
			return -1;
		}
	}
	return 0;
}

void LRMCleanup()
{
	if (g_top_manager == 0) {
		return;
	}
	destroy_resource_manager(g_top_manager);
	free_resource_manager(g_top_manager);
}


resid_t LRMCreate(int max, struct ResourcesOperation * opt, void * ctx)
{
	if (g_top_manager == 0) {
		LRMStartup(DEFAULT_MAX_LIMIT_RESOURCES_TYPE);
	}
	return create_resources_manager(max, opt, ctx);
}

void    LRMDestory(resid_t lrm)
{
	if (g_top_manager == 0) {
		return;
	}
	destory_resources_manager(lrm);
}

resid_t LRMAllocResources(resid_t lrm, void * ctx)
{
	if (g_top_manager == 0) {
		return INVALIDATE_ID;
	}
	return alloc_resource(lrm, -1, ctx);
}

void    LRMReleaseResources(resid_t lrm, resid_t res)
{
	if (g_top_manager == 0) {
		return;
	}
	release_resources(lrm, res);
}

void *  LRMGetResources(resid_t lrm, resid_t res)
{
	if (g_top_manager == 0) {
		return 0;
	}

	return get_resources(lrm, res);
}

void *  LRMSetResources(resid_t lrm, resid_t resid, void * res)
{
	if (g_top_manager == 0) {
		return 0;
	}
	return set_resources(lrm, resid, res);
}

resid_t LRMAllocResourcesAtPos(resid_t lrm, int pos, void * ctx)
{
	if (g_top_manager == 0) {
		return INVALIDATE_ID;
	}
	return alloc_resource(lrm, pos, ctx);
}
